View Javadoc
1   package org.apache.maven.surefire.common.junit48;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.Set;
29  
30  import org.apache.maven.shared.utils.io.SelectorUtils;
31  import org.apache.maven.surefire.booter.ProviderParameterNames;
32  import org.apache.maven.surefire.group.match.AndGroupMatcher;
33  import org.apache.maven.surefire.group.match.GroupMatcher;
34  import org.apache.maven.surefire.group.match.InverseGroupMatcher;
35  import org.apache.maven.surefire.group.parse.GroupMatcherParser;
36  import org.apache.maven.surefire.group.parse.ParseException;
37  import org.junit.experimental.categories.Category;
38  import org.junit.runner.Description;
39  import org.junit.runner.manipulation.Filter;
40  
41  /**
42   * @author Todd Lipcon
43   */
44  public class FilterFactory
45  {
46      private final ClassLoader testClassLoader;
47  
48      public FilterFactory( ClassLoader testClassLoader )
49      {
50          this.testClassLoader = testClassLoader;
51      }
52  
53      public Filter createGroupFilter( Properties providerProperties )
54      {
55          String groups = providerProperties.getProperty( ProviderParameterNames.TESTNG_GROUPS_PROP );
56          String excludedGroups = providerProperties.getProperty( ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP );
57  
58          GroupMatcher included = null;
59          if ( groups != null && groups.trim().length() > 0 )
60          {
61              try
62              {
63                  included = new GroupMatcherParser( groups ).parse();
64              }
65              catch ( ParseException e )
66              {
67                  throw new IllegalArgumentException( "Invalid group expression: '" + groups + "'. Reason: "
68                      + e.getMessage(), e );
69              }
70          }
71  
72          GroupMatcher excluded = null;
73          if ( excludedGroups != null && excludedGroups.trim().length() > 0 )
74          {
75              try
76              {
77                  excluded = new GroupMatcherParser( excludedGroups ).parse();
78              }
79              catch ( ParseException e )
80              {
81                  throw new IllegalArgumentException( "Invalid group expression: '" + excludedGroups + "'. Reason: "
82                      + e.getMessage(), e );
83              }
84          }
85  
86          if ( included != null && testClassLoader != null )
87          {
88              included.loadGroupClasses( testClassLoader );
89          }
90  
91          if ( excluded != null && testClassLoader != null )
92          {
93              excluded.loadGroupClasses( testClassLoader );
94          }
95  
96          return new GroupMatcherCategoryFilter( included, excluded );
97      }
98  
99      public Filter createMethodFilter( String requestedTestMethod )
100     {
101         return new MethodFilter( requestedTestMethod );
102     }
103 
104     public Filter createFailingMethodFilter( Map<Class<?>, Set<String>> failingClassMethodMap )
105     {
106         return new FailingMethodFilter( failingClassMethodMap );
107     }
108 
109     public Filter and( Filter filter1, Filter filter2 )
110     {
111         return new AndFilter( filter1, filter2 );
112     }
113 
114     private static class MethodFilter
115         extends Filter
116     {
117         private final String requestedTestMethod;
118 
119         public MethodFilter( String requestedTestMethod )
120         {
121             this.requestedTestMethod = requestedTestMethod;
122         }
123 
124         @Override
125         public boolean shouldRun( Description description )
126         {
127             for ( Description o : description.getChildren() )
128             {
129                 if ( isDescriptionMatch( o ) || shouldRun( o ) )
130                 {
131                     return true;
132                 }
133 
134             }
135             return isDescriptionMatch( description );
136         }
137 
138         private boolean isDescriptionMatch( Description description )
139         {
140             return description.getMethodName() != null
141                 && SelectorUtils.match( requestedTestMethod, description.getMethodName() );
142         }
143 
144         @Override
145         public String describe()
146         {
147             return "By method" + requestedTestMethod;
148         }
149     }
150 
151     // Only run test methods in the given input map, indexed by test class
152     private static class FailingMethodFilter
153         extends Filter
154     {
155         // Map from Class -> List of method names. Are the method names hashed to include the signature?
156         private final Map<Class<?>, Set<String>> failingClassMethodMap;
157 
158         public FailingMethodFilter( Map<Class<?>, Set<String>> failingClassMethodMap )
159         {
160             this.failingClassMethodMap = failingClassMethodMap;
161         }
162 
163         @Override
164         public boolean shouldRun( Description description )
165         {
166             return isDescriptionMatch( description );
167         }
168 
169         private boolean isDescriptionMatch( Description description )
170         {
171             if ( description.getTestClass() == null || description.getMethodName() == null )
172             {
173                 for ( Description childrenDescription : description.getChildren() )
174                 {
175                     if ( isDescriptionMatch( childrenDescription ) )
176                     {
177                         return true;
178                     }
179                 }
180                 return false;
181             }
182 
183             Set<String> testMethods = failingClassMethodMap.get( description.getTestClass() );
184             return testMethods != null && testMethods.contains( description.getMethodName() );
185         }
186 
187         @Override
188         public String describe()
189         {
190             return "By failing class method";
191         }
192     }
193 
194     private static class GroupMatcherCategoryFilter
195         extends Filter
196     {
197 
198         private final AndGroupMatcher matcher;
199 
200         public GroupMatcherCategoryFilter( GroupMatcher included, GroupMatcher excluded )
201         {
202             GroupMatcher invertedExclude = excluded == null ? null : new InverseGroupMatcher( excluded );
203             if ( included != null || invertedExclude != null )
204             {
205                 matcher = new AndGroupMatcher();
206                 if ( included != null )
207                 {
208                     matcher.addMatcher( included );
209                 }
210 
211                 if ( invertedExclude != null )
212                 {
213                     matcher.addMatcher( invertedExclude );
214                 }
215             }
216             else
217             {
218                 matcher = null;
219             }
220         }
221 
222         @Override
223         public boolean shouldRun( Description description )
224         {
225             if ( description.getMethodName() == null || description.getTestClass() == null )
226             {
227                 return shouldRun( description, null, null );
228             }
229             else
230             {
231                 return shouldRun( description, Description.createSuiteDescription( description.getTestClass() ),
232                                   description.getTestClass() );
233             }
234         }
235 
236         private static void findSuperclassCategories( Set<Class<?>> cats, Class<?> clazz )
237         {
238             if ( clazz != null && clazz.getSuperclass() != null )
239             {
240                 Category cat = clazz.getSuperclass().getAnnotation( Category.class );
241                 if ( cat != null )
242                 {
243                     Collections.addAll( cats, cat.value() );
244                 }
245                 else
246                 {
247                     findSuperclassCategories( cats, clazz.getSuperclass() );
248                 }
249             }
250         }
251 
252         private boolean shouldRun( Description description, Description parent, Class<?> parentClass )
253         {
254             if ( matcher == null )
255             {
256                 return true;
257             }
258             else
259             {
260                 Set<Class<?>> cats = new HashSet<Class<?>>();
261                 Category cat = description.getAnnotation( Category.class );
262                 if ( cat != null )
263                 {
264                     Collections.addAll( cats, cat.value() );
265                 }
266 
267                 if ( parent != null )
268                 {
269                     cat = parent.getAnnotation( Category.class );
270                     if ( cat != null )
271                     {
272                         Collections.addAll( cats, cat.value() );
273                     }
274                 }
275 
276                 if ( parentClass != null )
277                 {
278                     findSuperclassCategories( cats, parentClass );
279                 }
280 
281                 Class<?> testClass = description.getTestClass();
282                 if ( testClass != null )
283                 {
284                     cat = testClass.getAnnotation( Category.class );
285                     if ( cat != null )
286                     {
287                         Collections.addAll( cats, cat.value() );
288                     }
289                 }
290 
291                 cats.removeAll( Collections.<Class<?>>singleton( null ) );
292 
293                 boolean result = matcher.enabled( cats.toArray( new Class<?>[cats.size()] ) );
294 
295                 if ( !result )
296                 {
297                     ArrayList<Description> children = description.getChildren();
298                     if ( children != null )
299                     {
300                         for ( Description child : children )
301                         {
302                             if ( shouldRun( child, description, null ) )
303                             {
304                                 result = true;
305                                 break;
306                             }
307                         }
308                     }
309                 }
310 
311                 return result;
312             }
313         }
314 
315         @Override
316         public String describe()
317         {
318             return matcher == null ? "ANY" : matcher.toString();
319         }
320     }
321 
322     private static class AndFilter
323         extends Filter
324     {
325         private final Filter filter1;
326 
327         private final Filter filter2;
328 
329         public AndFilter( Filter filter1, Filter filter2 )
330         {
331             this.filter1 = filter1;
332             this.filter2 = filter2;
333         }
334 
335         @Override
336         public boolean shouldRun( Description description )
337         {
338             return filter1.shouldRun( description ) && filter2.shouldRun( description );
339         }
340 
341         @Override
342         public String describe()
343         {
344             return filter1.describe() + " AND " + filter2.describe();
345         }
346     }
347 
348     @SuppressWarnings( "unused" )
349     private static class CombinedCategoryFilter
350         extends Filter
351     {
352         private final List<Filter> includedFilters;
353 
354         private final List<Filter> excludedFilters;
355 
356         public CombinedCategoryFilter( List<Filter> includedFilters, List<Filter> excludedFilters )
357         {
358             this.includedFilters = includedFilters;
359             this.excludedFilters = excludedFilters;
360         }
361 
362         @Override
363         public boolean shouldRun( Description description )
364         {
365             return ( includedFilters.isEmpty() || inOneOfFilters( includedFilters, description ) )
366                 && ( excludedFilters.isEmpty() || !inOneOfFilters( excludedFilters, description ) );
367         }
368 
369         private boolean inOneOfFilters( List<Filter> filters, Description description )
370         {
371             for ( Filter f : filters )
372             {
373                 if ( f.shouldRun( description ) )
374                 {
375                     return true;
376                 }
377             }
378             return false;
379         }
380 
381         @Override
382         public String describe()
383         {
384             StringBuilder sb = new StringBuilder();
385             if ( !includedFilters.isEmpty() )
386             {
387                 sb.append( "(" );
388                 sb.append( joinFilters( includedFilters, " OR " ) );
389                 sb.append( ")" );
390                 if ( !excludedFilters.isEmpty() )
391                 {
392                     sb.append( " AND " );
393                 }
394             }
395             if ( !excludedFilters.isEmpty() )
396             {
397                 sb.append( "NOT (" );
398                 sb.append( joinFilters( includedFilters, " OR " ) );
399                 sb.append( ")" );
400             }
401 
402             return sb.toString();
403         }
404 
405         private String joinFilters( List<Filter> filters, String sep )
406         {
407             boolean isFirst = true;
408             StringBuilder sb = new StringBuilder();
409             for ( Filter f : filters )
410             {
411                 if ( !isFirst )
412                 {
413                     sb.append( sep );
414                 }
415                 sb.append( f.describe() );
416                 isFirst = false;
417             }
418             return sb.toString();
419         }
420     }
421 }